
#include "../global.h"
#include "../log/log.h"
#include "osocklib.h"


int OSOCK_get_free_udp_port(unsigned int port) {

  struct sockaddr_in addr_send;
  struct sockaddr_in addr_recv;
  struct timeval tv;
  int bind_retval_send = -1;
  int bind_retval_recv = -1;
  int sendto_retval = -1;
  int select_retval = -1;
  int setsockopt_retval = -1; 
  int sock_send = -1;
  int sock_recv = -1;
  const int on = 1;
  fd_set read_fds;
  char probe[] = "is the audience listenning?";

  if ( port < MIN_PORT ) {
    LOG_add(LOG_DEBUG, "OSOCK_get_free_udp_port: invalid starting port, now searching from port=MIN_PORT");
    port = MIN_PORT;
  }
 
  //give the random generator its seed
  srandom( (unsigned int)time( NULL ) ); 

  while ( select_retval != 0 ) {

    // generate random 0 to RAND_MAX offset, within available port range
    port = (port + (int)(random() % MAX_PORT_INCR)) % PORT_LIMIT;
    LOG_add(LOG_DEBUG, "OSOCK_get_free_udp_port: searching @ port %d", port);   
    
    sock_send = socket(AF_INET, SOCK_DGRAM, SELECT_PROTOCOL);
    
    if (sock_send == -1) { 
      LOG_add(LOG_ERROR, "OSOCK_get_free_udp_port: could not open sending UDP socket");
      return (-1);
    }
  
    setsockopt_retval = setsockopt(sock_send, SOL_SOCKET, SO_BROADCAST, (void*) &on, sizeof(on));
    
    if (setsockopt_retval == -1) {
      LOG_add(LOG_ERROR, "OSOCK_get_free_udp_port: setsockopt() failed");
      return (-1);
    }

    //setting IP, Family and Port for sending
    addr_send.sin_addr.s_addr = INADDR_BROADCAST;
    addr_send.sin_family = AF_INET;
    addr_send.sin_port = htons( (int)port );

    bind_retval_send = bind( sock_send, (const struct sockaddr *) &addr_send, sizeof(addr_send) );
    
    if (bind_retval_send == -1) {
      LOG_add(LOG_WARNING, "OSOCK_get_free_udp_port: bind() sending socket failed");
      continue;
    } 

    sendto_retval = sendto(sock_send, (const void*) probe, (size_t) sizeof(probe), SENDTO_UDP_FLAGS, (const struct sockaddr *) &(addr_send), (socklen_t) sizeof(addr_send) );
    
    if (sendto_retval == -1) {
      LOG_add(LOG_WARNING, "OSOCK_get_free_udp_port: sendto() failed on port %d", port);
      continue;
    }

    close(sock_send);
 
    sock_recv = socket(AF_INET, SOCK_DGRAM, SELECT_PROTOCOL);
    
    if (sock_recv == -1) { 
      LOG_add(LOG_ERROR, "OSOCK_get_free_udp_port: could not open receiving UDP socket");
      return (-1);
    }

    //setting IP, Family and Port for receiving
    addr_recv.sin_addr.s_addr = INADDR_ANY;
    addr_recv.sin_family = AF_INET;
    addr_recv.sin_port = htons( (int)port );

    bind_retval_recv = bind( sock_recv, (const struct sockaddr *) &addr_recv, sizeof(addr_recv) );
    
    if (bind_retval_recv == -1) {
      LOG_add(LOG_WARNING, "OSOCK_get_free_udp_port: bind() receiving socket failed");
      continue;
    }

    //creating and setting read file descriptor set for select()
    FD_ZERO(&read_fds);
    FD_SET(sock_recv, &read_fds);

    //timeout of UDP_SEARCH_TIMEOUT seconds for select() 
    //need to be done every time we loop (RTFM)
    memset(&tv,0,sizeof(struct timeval));   
    tv.tv_sec = UDP_SEARCH_TIMEOUT;
    
    select_retval = select( ((int)sock_recv + 1), &read_fds, NULL, NULL, &tv);

  }
       
  if ( (FD_ISSET(sock_recv, &read_fds) == 0) ) {
    close(sock_recv);
    LOG_add(LOG_DEBUG, "OSOCK,get_free_udp_port: port %d was found to be free", port);
    return (port);
  }
  
  else {
    close(sock_recv);
    LOG_add(LOG_WARNING, "OSOCK,get_free_udp_port: no free UDP port found .. hm .. alil' strange");
    return (-1); 
  }

  close(sock_recv);
  LOG_add(LOG_ERROR, "OSOCK_get_free_udp_port: unknown error");
  return (port);

}


int OSOCK_same_subnet(const char* ipv4) {
 
  if (ipv4 == NULL) {
    LOG_add(LOG_WARNING, "OSOCK_same_subnet: NULL ipv4 address pointer");
    return (-1);
  }

  LOG_add(LOG_DEBUG, "OSOCK_same_subnet: no test performed, let us assume that remote host IS on same subnet");
  return (0);

}


int OSOCK_connect(msd_s* msocket, const protocol_t proto, const char* ipv4addr, in_port_t port, const sockmode_t mode) {

  int connect_retval = -1;
  int bind_retval = -1;
  int listen_retval = -1;
  int setsockopt_retval = -1;
  const int on = 1;

  msocket->domain = AF_INET; 
  msocket->protocol = SELECT_PROTOCOL;

  if (proto == TCP) {
 
    LOG_add(LOG_DEBUG, "OSOCK_connect: proto is TCP");
    msocket->type = SOCK_STREAM;
    msocket->sd = socket(msocket->domain, msocket->type, msocket->protocol);
    LOG_add(LOG_DEBUG, "OSOCK_connect: socket() call done");

    if (msocket->sd == -1) {
      LOG_add(LOG_ERROR, "OSOCK_connect: could not open TCP socket");
      return (-1);
    }

    if (mode == CLIENT) {
      
      LOG_add(LOG_DEBUG, "OSOCK_connect: mode is CLIENT");
      msocket->mode = CLIENT;

      //filling sockaddr_in structure with IP, Family and Port information
      msocket->addr.sin_addr.s_addr=inet_addr((const char*) ipv4addr);      
      msocket->addr.sin_family = AF_INET;
      msocket->addr.sin_port = htons( (int)port );
      
      LOG_add(LOG_DEBUG, "OSOCK_connect: got everything needed to connect()");
      connect_retval = connect(msocket->sd, (const struct sockaddr *) &(msocket->addr), sizeof(msocket->addr));
      LOG_add(LOG_DEBUG, "OSOCK_connect: connect() call done");

      if ((connect_retval == -1) && (errno == ECONNREFUSED)) {
        LOG_add(LOG_WARNING, "OSOCK_connect: opening TCP socket failed, remote host is refusing connection");
        return (-1);
      }
      else if (connect_retval == -1) {
        LOG_add(LOG_WARNING, "OSOCK_connect: failed to open TCP socket on remote host");
        return (-1);
      }
      else if (connect_retval == 0) {
        LOG_add(LOG_DEBUG, "OSOCK_connect: TCP socket opened on remote host");
        return (0);
      }
      else {
      LOG_add(LOG_ERROR, "OSOCK_connect: unknown error");
      return (-1);
      }
      
    }

    if (mode == SERVER) {

      LOG_add(LOG_DEBUG, "OSOCK_connect: mode is SERVER");
      msocket->mode = SERVER;

      //filling sockaddr_in structure with IP, Family and Port information
      msocket->addr.sin_addr.s_addr = INADDR_ANY;
      msocket->addr.sin_family = AF_INET;
      msocket->addr.sin_port = htons( (int)port );

      LOG_add(LOG_DEBUG, "OSOCK_connect: ready to bind() socket to port");
      bind_retval = bind( msocket->sd, (const struct sockaddr *) &(msocket->addr), sizeof(msocket->addr) );     
      LOG_add(LOG_DEBUG, "OSOCK_connect: bind() call done");

      if (bind_retval == -1) {
        LOG_add(LOG_ERROR, "OSOCK_connect: could not bind TCP socket to port");
        return (-1);
      }
      
      LOG_add(LOG_DEBUG, "OSOCK_connect: listen() call");
      listen_retval = listen(msocket->sd, LISTEN_PENDING_CONNECT);
      LOG_add(LOG_DEBUG, "OSOCK_connect: listen() call done");

      if (listen_retval == -1) {
        LOG_add(LOG_ERROR, "OSOCK_connect: could not listen on that socket");
        return (-1);
      }
           
      return (0);
    }
  
  }

  if (proto == UDP) {
   
    LOG_add(LOG_DEBUG, "OSOCK_connect: proto is UDP");
    msocket->type = SOCK_DGRAM;
    LOG_add(LOG_DEBUG, "OSOCK_connect: socket() call");
    msocket->sd = socket(msocket->domain, msocket->type, msocket->protocol);
    LOG_add(LOG_DEBUG, "OSOCK_connect: socket() call done");

    if (msocket->sd == -1) {
      LOG_add(LOG_ERROR, "OSOCK_connect: could not open UDP socket");
      return (-1);
    }
    
    if (mode == CLIENT) {

      LOG_add(LOG_DEBUG, "OSOCK_connect: mode is CLIENT");
      msocket->mode = CLIENT;

      //filling sockaddr_in structure with IP, Family and Port information
      msocket->addr.sin_addr.s_addr = INADDR_ANY;
      msocket->addr.sin_family = AF_INET;
      msocket->addr.sin_port = htons( (int)port );     
 
      LOG_add(LOG_DEBUG, "OSOCK_connect: ready to bind() socket to port");
      bind_retval = bind( msocket->sd, (const struct sockaddr *) &(msocket->addr), sizeof(msocket->addr) );     
      LOG_add(LOG_DEBUG, "OSOCK_connect: bind() call done");

      if (bind_retval == -1) {
        LOG_add(LOG_ERROR, "OSOCK_connect: could not bind TCP socket to port");
        return (-1);
      }
     
      LOG_add(LOG_DEBUG, "OSOCK_connect: UDP client done");
      return (0);
    
    }

    if (mode == SERVER) {
      
      LOG_add(LOG_DEBUG, "OSOCK_connect: mode is SERVER");
      msocket->mode = SERVER;
    
      LOG_add(LOG_DEBUG, "OSOCK_connect: setsockopt() call: enabling UDP broadcast in SERVER mode"); 
      setsockopt_retval = setsockopt(msocket->sd, SOL_SOCKET, SO_BROADCAST, (void*) &on, sizeof(on));
      LOG_add(LOG_DEBUG, "OSOCK_connect: setsockopt() call done");

      if (setsockopt_retval == -1) {
        LOG_add(LOG_ERROR, "OSOCK_connect: setsockopt() failed");
        return (-1);
      }

      //filling sockaddr_in structure with IP (broadcast), Family and Port information
      msocket->addr.sin_addr.s_addr = INADDR_BROADCAST;
      msocket->addr.sin_family = AF_INET;
      msocket->addr.sin_port = htons( (int)port );

      LOG_add(LOG_DEBUG, "OSOCK_connect: ready to bind() socket to port");
      bind_retval = bind( msocket->sd, (const struct sockaddr *) &(msocket->addr), sizeof(msocket->addr) );     
      LOG_add(LOG_DEBUG, "OSOCK_connect: bind() call done");

      if (bind_retval == -1) {
        LOG_add(LOG_ERROR, "OSOCK_connect: could not bind TCP socket to port");
        return (-1);
      }

      LOG_add(LOG_DEBUG, "OSOCK_connect: UDP server done");
      return (0);

    }
  
  }
  
  LOG_add(LOG_ERROR, "OSOCK_connect: unknown error");
  return (-1);

}


int OSOCK_validate_tcp_server(msd_s* msocket) {

  int temp_sd = -1;
  socklen_t socklen_tmp = -1;

  if (msocket == NULL) {
    LOG_add(LOG_ERROR, "OSOCK_validate_tcp_server: invalid msocket pointer");
    return (-1);
  }

  LOG_add(LOG_DEBUG, "OSOCK_validate_tcp_server: accept() call");
  socklen_tmp = sizeof(msocket->addr);
  temp_sd = accept( msocket->sd, (struct sockaddr *) &(msocket->addr), &socklen_tmp );
  LOG_add(LOG_DEBUG, "OSOCK_validate_tcp_server: accept() call done");

  if (temp_sd == -1) {
    LOG_add(LOG_ERROR, "OSOCK_validate_tcp_server: failed to accept connections on that socket");
    return (-1);
  }
  
  msocket->sd = temp_sd;
  LOG_add(LOG_DEBUG, "OSOCK_validate_tcp_server: waiting socket replaced by current active socket");
  return (0);

}


int OSOCK_disconnect(msd_s* msocket) {
  
  int retval = -1;
  retval = close(msocket->sd);

  if ((retval == -1) && (errno == EBADF)) {
    LOG_add(LOG_ERROR, "OSOCK_disconnect: bad socket descriptor, could not disconnect");
    return (-1);
  }
  if (retval == -1) {
    LOG_add(LOG_ERROR, "OSOCK_disconnect: could not disconnect");
    return (-1);
  }
  if (retval == 0) {
    LOG_add(LOG_DEBUG, "OSOCK_disconnect: msocket disconnected");
    return (0);
  }
  
  LOG_add(LOG_ERROR, "OSOCK_disconnect: unknown error");
  return (-1);

}


int OSOCK_send(const msd_s* msocket, const npacket_s* packet) {

  ssize_t retval = -1;
 
  if ((int)packet->datalen > MAX_NPACK_SIZE) {
    LOG_add(LOG_WARNING, "OSOCK_send: packet exceeding MAX_NPACK_SIZE");
    return (-1);
  }
  
  if (msocket->type == SOCK_STREAM) {
    LOG_add(LOG_DEBUG, "OSOCK_send: sending in TCP");
    retval = sendto(msocket->sd, (const void*) packet->datap, (size_t) packet->datalen, SENDTO_TCP_FLAGS, NULL, 0);
  }
  
  if (msocket->type == SOCK_DGRAM) {
    
    LOG_add(LOG_DEBUG, "OSOCK_send: sending in UDP");
    retval = sendto(msocket->sd, (const void*) packet->datap, (size_t) packet->datalen, SENDTO_UDP_FLAGS, (const struct sockaddr *) &(msocket->addr), (socklen_t) sizeof(msocket->addr) );
  }

  if ((int)retval == -1) {
    LOG_add(LOG_ERROR, "OSOCK_send: error, nothing sent");
    return (-1);
  }
  if (retval == packet->datalen) {
    LOG_add(LOG_DEBUG, "OSOCK_send: packet sent");
    return (0);
  }

  LOG_add(LOG_ERROR, "OSOCK_send: unknown error");
  return (-1);

}


int OSOCK_receive(npacket_s* packet, const msd_s* msocket) {

  ssize_t retval = -1;
  socklen_t socklen_tmp;

  if (msocket->type == SOCK_STREAM) {
    LOG_add(LOG_DEBUG, "OSOCK_receive: receiving in TCP");
    retval = recvfrom(msocket->sd, (void*) packet->datap, (size_t) MAX_NPACK_SIZE, RECV_TCP_FLAGS, NULL, NULL);
  }

  if (msocket->type == SOCK_DGRAM) {
    
    LOG_add(LOG_DEBUG, "OSOCK_receive: receiving in UDP");
    socklen_tmp = sizeof(msocket->addr);

    retval = recvfrom(msocket->sd, (void*) packet->datap, (size_t) MAX_NPACK_SIZE, RECV_UDP_FLAGS, (struct sockaddr *) &(msocket->addr), &socklen_tmp );
  
    //from_ipv4 = inet_ntoa(msocket->addr.sin_addr)+4;
  }

  if (((int)retval == -1) && (errno == EBADF)) {
    LOG_add(LOG_ERROR, "OSOCK_receive: bad socket descriptor, nothing received");
    return (-1);
  }
  if (((int)retval == -1) && (errno == EAGAIN)) {
    LOG_add(LOG_WARNING, "OSOCK_receive: timeout, nothing received");
    return (-1);
  }
  if ((int)retval == -1) {
    LOG_add(LOG_ERROR, "OSOCK_receive: error, nothing received");
    return (-1);
  }
  if ((int)retval == 0) {
    LOG_add(LOG_WARNING, "OSOCK_receive: peer has performed an orderly shutdown");
    packet->datalen = 0; 
    return (0);
  }
  if (((int)retval > 0) && ((int)retval >= MAX_NPACK_SIZE)) {
    LOG_add(LOG_ERROR, "OSOCK_receive: packet received but exceeding MAX_NPACK_SIZE, setting datalen to 0");
    packet->datalen = 0;
    return (0);
  }
  if (((int)retval > 0) && ((int)retval < MAX_NPACK_SIZE)) {
    LOG_add(LOG_DEBUG, "OSOCK_receive: packet received");
    packet->datalen = retval;
    return (1);
  }

  LOG_add(LOG_ERROR, "OSOCK_receive: unknown error");
  return (-1);

}


size_t OSOCK_get_npacket_length(const npacket_s* packet) {
  
  if ((int)packet->datalen >= 0) {
    if ((int)packet->datalen <= MAX_NPACK_SIZE) {
      //length >= 0 and <= MAX, everything is OK
      LOG_add(LOG_DEBUG, "OSOCK_get_packet_length: valid network packet length");
      return (packet->datalen);
    }
    else {
      //length >= 0 but > MAX, there IS a problem
      LOG_add(LOG_WARNING, "OSOCK_get_packet_length: invalid network packet length (>MAX_NPACK_SIZE)");
      return ((size_t) -1);
    }
  }
  else {
    //length < 0, there IS a problem
    LOG_add(LOG_WARNING, "OSOCK_get_packet_length: invalid network packet length (<0)");
    return ((size_t) -1);
  }
  
  LOG_add(LOG_ERROR, "OSOCK_get_packet_length: unknown error");
  return ((size_t) -1);

}


void* OSOCK_get_npacket_data(npacket_s* packet) {
  
  if (packet->datap != NULL) {
    LOG_add(LOG_DEBUG, "OSOCK_get_packet_data: network packet data pointer appears to be valid");
    return ((void*)packet->datap);
  }
  else {
    LOG_add(LOG_WARNING, "OSOCK_get_packet_data: network packet data pointer is NULL");
    return (NULL);
  }
  
  LOG_add(LOG_ERROR, "OSOCK_get_packet_data: unknown error");
  return (NULL);
  
}


int OSOCK_fill_npacket(npacket_s* packet, const void* data, const size_t len) {

  if( (data != NULL) && ((int)len >= 0) && ((int)len <= MAX_NPACK_SIZE) ) {
    packet->datap = (void*)data;
    packet->datalen = len;
    LOG_add(LOG_DEBUG, "OSOCK_fill_packet: network packet filled");
    return (0);
  }
  else {
    LOG_add(LOG_WARNING, "OSOCK_fill_packet: invalid data pointer or data length");
    return (-1);
  }

  LOG_add(LOG_ERROR, "OSOCK_fill_packet: unknown error");
  return (-1);

}


int OSOCK_watch_connection(const msd_s* msocket, long timeout) {
  
  fd_set read_fds;
  struct timeval tv;
  int select_retval = -1;

  if ( (msocket->type != SOCK_STREAM) || (msocket->mode != SERVER) || (msocket->sd <= 0) ) {
    LOG_add(LOG_WARNING, "OSOCK_watch_connection: not a TCP socket or not a SERVER socket or invalid socket pointer");
    return (-1);
  }
    
  tv.tv_sec = timeout;
  tv.tv_usec = 0;
 
  FD_ZERO(&read_fds);
  FD_SET(msocket->sd, &read_fds);

  if (timeout == 0) {
    select_retval = select( FD_SETSIZE, &read_fds, NULL, NULL, NULL); 
  }
  else {
    select_retval = select( FD_SETSIZE, &read_fds, NULL, NULL, &tv);
  }

  if (FD_ISSET(msocket->sd, &read_fds)) {
    LOG_add(LOG_DEBUG, "OSOCK_watch_connection: 1 awaiting connection");
    return (0);
  }

  if ( select_retval == 0 ) {
    LOG_add(LOG_WARNING, "OSOCK_watch_connection: connection timeout");
    return (-1);
  }
  else {
    LOG_add(LOG_ERROR, "OSOCK_watch_connection: select() error");
    return (-1); 
  }

  LOG_add(LOG_ERROR, "OSOCK_watch_connection: unknown error");
  return (-1);

}


/* end of osocklib.c */
